﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;


namespace AIthin.Neuro
{
         /// <summary>
        /// Base neural layer class.
        /// </summary>
        ///  
        /// <remarks>This is a base neural layer class, which represents
        /// collection of neurons.</remarks>
        /// 
        [Serializable]
        public class Network
        {
            public List<int> ComputatedNeurons = new List<int>();
            public int inputsCount = 0;
            public int outputsCount = 0;
            protected int neuronsCount = 0;
            protected static Random random = new Random();
            protected Neuron[] neurons;
            public int InputsCount
            {
                get { return inputsCount; }
            }
            public int NeuronsCount
            {
                get { return neuronsCount; }
            }
           
            /// <summary>
            /// Initializes a new instance of the <see cref="Network"/> class.
            /// </summary>
            /// 
            /// <param name="neuronsCount">Network's neurons count.</param>
            /// <param name="inputsCount">Network's inputs count.</param>
            /// 
            /// <remarks>Protected contructor, which initializes <see cref="inputsCount"/>,
            /// <see cref="neuronsCount"/> and <see cref="neurons"/> members.</remarks>
            /// 
            public Network(int neuronsCount, int inputsCount, int outputsCount)
            {
                this.inputsCount = Math.Max(1, inputsCount);
                this.outputsCount = Math.Max(1, outputsCount);
                this.neuronsCount = Math.Max(1, neuronsCount);
                // create collection of neuron
                neurons = new Neuron[this.neuronsCount + this.inputsCount + this.outputsCount];
                // The first neurons at Input, amout -> InputsCount
                for (int i = 0; i < inputsCount; i++)
                {
                    this.neurons[i] = new Neuron(i, 1, 4);
                    Debug.WriteLine(neurons[i].ID, "input neuron");
                }

                neuronsCount += inputsCount;

                for (int i = inputsCount; i < neuronsCount; i++)
                {
                    this.neurons[i] = new Neuron(i, 16, 4);
                    //TODO build Network
                    int j = 0;
                    int k = 0;
                    while (j < neurons[i].inputsCount)
                    {
                        //make sure that neuron isnt twice connected at the same neuron
                        if (neurons[i].Connect(ref neurons[random.Next(0, inputsCount)]))
                            j++;
                        else
                            if (++k > 4 * inputsCount)
                                throw new NotImplementedException("no free connection");
                    }
                    Debug.WriteLine(" neuron " + neurons[i].ID.ToString() + " connected");

                }
                for (int i = neuronsCount; i < neuronsCount + outputsCount; i++)
                {
                    // output neurons
                    this.neurons[i] = new Neuron(i, 3, 1);
                    //TODO build Network
                    int j = 0;
                    int k = 0;
                    while (j < neurons[i].inputsCount)
                    {
                        //make sure that neuron isnt twice connected at the same neuron
                        if (neurons[i].Connect(ref neurons[random.Next(inputsCount, neuronsCount)]))
                            j++;
                        else
                            if (++k > 4 * inputsCount)
                                throw new NotImplementedException("no free connection");
                    }
                    Debug.WriteLine(" output neuron " + neurons[i].ID.ToString() + " connected");
                }

                neuronsCount += outputsCount;
            }
            public void SetInput(ref double[] input)
            {
                for (int i = 0; i < inputsCount; i++)
                {
                    // ! Input index equal with the neuron number
                    neurons[i].SetInput(input[i]);
                }
            }
            public void SetOutput(ref double[] output)
            {
                for (int i =0;  i < outputsCount; i++)
                {
                    // ! Input index equal with the neuron number
                    Debug.WriteLine(i, "set output");
                    neurons[neuronsCount-outputsCount+i].SetOutput(output[i]);
                }
            }
            /// <summary>
            /// Calculate one step
            /// SetInput(input);
            /// Prosess input signal from input neurons to output neurons
            ///at first process the list of start neurons (input)
            /// than each next neuron for each output
            /// start moment to calculate neuron ist when all inputs are aktiv with strong or not strong signal on input
            /// bool [] InputActiv
            /// 1. Activate Inputs (neuron[i].set_input(2,signal);)
            /// 2. Loop thru all neurons
            /// 3. calculate activ neurons
            /// 3a. note calculation path vor learning and note activ neurons.
            /// 4. set outputs
            /// 5. activate inputs
            /// 6.
            /// go to 2.
            /// when all neurons calculated
            /// calculate shortest pathses and learn that neurons that were activ
            /// second network calculate future next step and compare with reality and leran
            /// expand network and compress network
            /// neuron can be some ticks activ 
            /// </summary>
            /// <param name="input">input signals</param>
            /// <param name="output">superwised output signals for comparsion</param>
            public void Tick(ref double[] input, ref double[] output)
            {
                // set input signal
                SetInput(ref input);
                // compute neurons state
                // activation welle 
                Compute();
                // set output (activated output neurons)
                SetOutput(ref output);
                // learn 
                ReverseComputation();
                // TODO 
                Reset();
               
            }
            public void Tick(ref double[] input)
            {
                // set input signal
                this.SetInput(ref input);
                // compute neurons state
                // activation welle 
                this.Compute();
            }
            private void Compute()
            {
                // activation wave
                while (ComputatedNeurons.Count <= neuronsCount)
                {
                    foreach (Neuron neuron in neurons)
                    {
                        if (neuron.Computate())
                        {
                            ComputatedNeurons.Add(neuron.ID);
                            Debug.WriteLine(neuron.ID, " computated neuron");
                            foreach (AxonTerminal a in neuron.outputs) neurons[a.ConnectedNeuron].ActivateInput(a.ConnectedDendrit);
                        }
                    }
                }
            }
            private void ReverseComputation()
            {
                //TODO learn -> make connections, correct threshould and weights
                ComputatedNeurons.Reverse();

                foreach (int i in ComputatedNeurons)
                {
                    neurons[i].ReverseComputation();
                }
            }
            private void Reset()
            {
                //clear list of computated neurons 
                ComputatedNeurons.Clear();
                //TODO That is false! Neurons must go out naturally. Make a subprog EndComputate();
                foreach (Neuron neuron in neurons)
                {
                    neuron.AllInputsActivated = false;
                    neuron.Activated = false;
                }
            }
            void Add(Neuron neuron)
            {
               
            }
            List<int> FindPath(int from, int to)
            {
                List<int> Path = new List<int>();
                Path.Add(from);
               //TODO Find Path
                return Path;
            }
            /// <summary>
            /// Save network to specified file.
            /// </summary>
            /// 
            /// <param name="fileName">File name to save network into.</param>
            /// 
            /// <remarks><para>The neural network is saved using .NET serialization (binary formatter is used).</para></remarks>
            /// 
            public void Save(string fileName)
            {
                FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None);
                Save(stream);
                stream.Close();
            }

            /// <summary>
            /// Save network to specified file.
            /// </summary>
            /// 
            /// <param name="stream">Stream to save network into.</param>
            /// 
            /// <remarks><para>The neural network is saved using .NET serialization (binary formatter is used).</para></remarks>
            /// 
            public void Save(Stream stream)
            {
                IFormatter formatter = new BinaryFormatter();
                formatter.Serialize(stream, this);
            }

            /// <summary>
            /// Load network from specified file.
            /// </summary>
            /// 
            /// <param name="fileName">File name to load network from.</param>
            /// 
            /// <returns>Returns instance of <see cref="Network"/> class with all properties initialized from file.</returns>
            /// 
            /// <remarks><para>Neural network is loaded from file using .NET serialization (binary formater is used).</para></remarks>
            /// 
            public static Network Load(string fileName)
            {
                FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
                Network network = Load(stream);
                stream.Close();

                return network;
            }

            /// <summary>
            /// Load network from specified file.
            /// </summary>
            /// 
            /// <param name="stream">Stream to load network from.</param>
            /// 
            /// <returns>Returns instance of <see cref="Network"/> class with all properties initialized from file.</returns>
            /// 
            /// <remarks><para>Neural network is loaded from file using .NET serialization (binary formater is used).</para></remarks>
            /// 
            public static Network Load(Stream stream)
            {
                IFormatter formatter = new BinaryFormatter();
                Network network = (Network)formatter.Deserialize(stream);
                return network;
            }
        }
   
}
